package ro.gillardon.business.priceservice.impl.plane;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

import ro.gillardon.commons.Offers;
import ro.gillardon.commons.Price;
import ro.gillardon.commons.config.ConfigParam;
import ro.gillardon.commons.config.ConfigProvider;
import ro.gillardon.commons.enums.Company;
import ro.gillardon.commons.enums.Currency;
import ro.gillardon.commons.logger.BTPLogger;
import ro.gillardon.commons.mot.plane.PlaneConditions;
import ro.gillardon.commons.mot.plane.PlaneOfferDetails;

/**
 * Class for parsing the raw HTML result list, <br/>
 * returned by navigator into useful PlaneOffer objects.
 */
public final class PlaneResultParser {

  /** the lo4j Logger. */
  private static final Logger LOG = Logger.getLogger(PlaneResultParser.class);

  /** The Company xpath. */
  private static final String COMPANY = getProperty(ConfigParam.BUSINESS_PRICESERVICE_PLANE_PARSER_XPATH_COMPANY);

  /** The all Flight Times xpath. */
  private static final String FLIGHT_TIMES = getProperty(ConfigParam.BUSINESS_PRICESERVICE_PLANE_PARSER_XPATH_FLIGHTS);

  /** The prices for the filghts xpath. */
  private static final String PRICES = getProperty(ConfigParam.BUSINESS_PRICESERVICE_PLANE_PARSER_XPATH_PRICES);

  /** The number of pannels on the UI for the travel times. */
  private static final int NR_OF_TIME_COMPONENTS = Integer
    .parseInt(getProperty(ConfigParam.BUSINESS_PRICESERVICE_PLANE_PARSER_NO_DIVISION));

  /** The document that will be parsed. */
  private static Document document;

  /**
   * Helper method to initialize fields.
   *
   * @param param the configParam
   * @return the property value
   */
  private static String getProperty(final ConfigParam param) {
    return ConfigProvider.getProperty(param);
  }

  /**
   * Instantiates a new plane result parser.
   */
  private PlaneResultParser() {
    // default constructor
  }

  /**
   * Parses the raw HTML Source.
   *
   * @param source the raw HTML source
   * @param conditions the plane conditions
   * @return the offers
   */
  public static Offers<PlaneOfferDetails> parse(final String source, final PlaneConditions conditions) {

    try {
      document = Jsoup.parse(source);
      List<PlaneOfferDetails> offers = new ArrayList<PlaneOfferDetails>();
      PlaneOfferDetails planeCurrentOffer = new PlaneOfferDetails();

      planeCurrentOffer.setCompany(getCompany());
      planeCurrentOffer.setFrom(conditions.getFrom());
      planeCurrentOffer.setTo(conditions.getTo());

      int howManyOffers = document.select(FLIGHT_TIMES).size();
      for (int i = 0; i < howManyOffers; i = i + NR_OF_TIME_COMPONENTS) {

        createCompletedOffer(conditions, planeCurrentOffer, i);

        BTPLogger.error(LOG, planeCurrentOffer.toString());
        offers.add(planeCurrentOffer);
      }

      return new Offers<PlaneOfferDetails>(offers);
    } catch (IllegalArgumentException e) {
      BTPLogger.error(LOG, "Source file is NULL !. Details :" + e);
      List<PlaneOfferDetails> emptyList = new ArrayList<PlaneOfferDetails>();
      return new Offers<PlaneOfferDetails>(emptyList);
    }

  }

  /**
   * Completes the dates and the prices for the offers.
   *
   * @param conditions the plane conditions
   * @param planeCurrentOffer the focused offer that needs to be completed
   * @param i index of the current focused offer
   */
  private static void createCompletedOffer(final PlaneConditions conditions, final PlaneOfferDetails planeCurrentOffer,
    final int i) {

    planeCurrentOffer.setDepartStart(createTime(conditions.getDepartureDate(), i));
    planeCurrentOffer.setDepartEnd(createTime(conditions.getDepartureDate(), i + 1));
    planeCurrentOffer.setReturnStart(createTime(conditions.getArrivalDate(), i + 2));
    planeCurrentOffer.setReturnEnd(createTime(conditions.getArrivalDate(), i + 3));

    planeCurrentOffer.setPrice(getPrice(i / NR_OF_TIME_COMPONENTS));

  }

  /**
   * Merges a LocalDate and a LocalTime into a LocalDateTime object.
   *
   * @param localDate from the plane conditions
   * @param i index of the current focused offer
   * @return complete date of the current flight
   */
  private static LocalDateTime createTime(final LocalDate localDate, final int i) {
    return LocalDateTime.of(localDate, getFlightTimesForFlight(i));
  }

  /**
   * Pares the HTML and finds the company of the direct flights that are listed.
   *
   * @return company name
   */
  private static Company getCompany() {
    Elements company = document.select(COMPANY);
    if (company.select("span").first() == null) {
      return Company.LUFTHANSA;
    } else if (company.select("img").first() == null) {
      return Company.WIZZAIR;
    }
    return null;
  }

  /**
   * Converts the flight times (HH:mm) into a LocalTime object.
   *
   * @param index of the current focused offer
   * @return formated date string
   */
  private static LocalTime getFlightTimesForFlight(final Integer index) {
    Elements flightTimes = document.select(FLIGHT_TIMES);
    String[] hoursMinutes = flightTimes.get(index).text().split(":");
    return LocalTime.of(Integer.parseInt(hoursMinutes[0]), Integer.parseInt(hoursMinutes[1]));
  }

  /**
   * Pares the HTML and finds the price of the direct flights that are listed.
   *
   * @param index the index
   * @return price of the flight
   */
  private static Price getPrice(final Integer index) {
    Elements price = document.select(PRICES);
    String[] priceSplit = price.get(index).text().split(" ");

    double priceConverted = Double.parseDouble(priceSplit[0]);

    return new Price(priceConverted, Currency.EUR);
  }
}
